/*
 *  ReflectiveDock.mm
 *  
 *
 *  Created by David Ashman on 1/12/09.
 *  Copyright 2009 David Ashman. All rights reserved.
 *
 */

#include <substrate.h>
#import <SpringBoard/SBApplication.h>
#import <SpringBoard/SBApplicationIcon.h>
#import <SpringBoard/SBBookmarkIcon.h>
#import <SpringBoard/SBIconController.h>
#import <SpringBoard/SBIconList.h>
#import <SpringBoard/SBCalendarController.h>
#import <SpringBoard/CDStructures.h>
#import <UIKit/UIKit.h>
 
static Class $SBIconModel = objc_getClass("SBIconModel");
static Class $SBIconLabel = objc_getClass("SBIconLabel");
static Class $SBIconImageView = objc_getClass("SBIconImageView");
static Class $SBIconBadge = objc_getClass("SBIconBadge");
static Class $SBIconController;

static double _lastSettingsLoad = 0;
static bool _reflectDock = true;
static bool _reflectApps = false;
static int _gap = 0;
static int _horizontalShift = 0;
static float _opacity = 0.8;
static int _length = 0;
static float _lengthFactor = 0.45;
static bool _useGradient = true;
static BOOL jittering = false;
static NSMutableSet* _reflectionViews;

@interface RDReflectionView : UIView

@property (nonatomic, retain) UIImage* reflection;

@end

static void initSettingsForFile(NSString* file)
{
	NSLog(@"RD: Loading settings from %@", file);
	if (file)
	{
		NSDictionary *settings = [[NSDictionary alloc] initWithContentsOfFile:file];
	       	[settings autorelease];

		if (NSNumber *reflect = [settings objectForKey:@"Enabled"])
		{
			_reflectDock = [reflect boolValue];
			_reflectApps = _reflectDock;
		}

		if (NSNumber *reflect = [settings objectForKey:@"ReflectDock"])
       		     	_reflectDock = [reflect boolValue];

		NSLog(@"RD: Reflect Dock: %d", _reflectDock);
				
		if (NSNumber *gradient = [settings objectForKey:@"UseGradient"])
       		     	_useGradient = [gradient boolValue];
		NSLog(@"RD: Use Gradient: %d", _useGradient);
				
       		if (NSNumber *shift = [settings objectForKey:@"HorizontalShift"])
       		 	_horizontalShift = [shift intValue];
		NSLog(@"RD: Horizontal Shift: %d", _horizontalShift);
	
       		if (NSNumber *gap = [settings objectForKey:@"ReflectionGap"])
       		 	_gap = [gap intValue];
		NSLog(@"RD: Gap: %d", _gap);
	
       	 	if (NSNumber *opacity = [settings objectForKey:@"Opacity"])
       	     		_opacity = [opacity floatValue];
		NSLog(@"RD: Opacity: %f", _opacity);

		if (NSNumber *length = [settings objectForKey:@"ReflectionLength"])
       		     	_length = [length intValue];
		NSLog(@"RD: Reflection Length: %d", _length);

		if (NSNumber *lengthFactor = [settings objectForKey:@"ReflectionLengthFactor"])
       		     	_lengthFactor = [lengthFactor floatValue];
		NSLog(@"RD: Reflection Factor: %f", _lengthFactor);

		if (NSNumber *reflectAll = [settings objectForKey:@"ReflectAllIcons"])
       		     	_reflectApps = [reflectAll boolValue];

		if (NSNumber *reflectAll = [settings objectForKey:@"ReflectApps"])
       		     	_reflectApps = [reflectAll boolValue];

		NSLog(@"RD: Reflect Apps: %d", _reflectApps);

		NSLog(@"RD: Done loading settings");
	}
}
 
static void initSettings()
{
	// since we're depending on WinterBoard for this, we need to wait until the first icon is
	// loaded to find the settings
    	if (_lastSettingsLoad == 0)
	{
		NSString* pathToSettings = @"/User/Library/Preferences/com.ashman.ReflectiveDock.plist";
		initSettingsForFile(pathToSettings);

		pathToSettings = [[NSBundle mainBundle] pathForResource:@"com.ashman.ReflectiveDock" ofType:@"plist"];
		initSettingsForFile(pathToSettings);

		_lastSettingsLoad = [NSDate timeIntervalSinceReferenceDate];
	}
}

static UIView* createReflection(SBIcon *icon) 
{
	UIView* reflectView = [icon viewWithTag:90210];

	if (reflectView != nil)
		return reflectView;

        initSettings();

	UIImage* fromImage = [icon icon];
	CGRect rect = CGRectMake(_horizontalShift - ((fromImage.size.width - icon.frame.size.width) / 2), 0, fromImage.size.width, fromImage.size.height * 2);

	reflectView = [[RDReflectionView alloc] initWithFrame:rect];
	reflectView.tag = 90210;
	reflectView.alpha = _opacity;
	reflectView.opaque = NO;
	reflectView.hidden = NO;
	reflectView.userInteractionEnabled = NO;
	[icon insertSubview:reflectView atIndex:0];
	[_reflectionViews addObject:reflectView];

	return reflectView;
}

MSHook(void, reloadIconImageForDisplayIdentifier, SBIconModel *self, SEL sel, NSString* id) 
{
	_reloadIconImageForDisplayIdentifier(self, sel, id);
	if (SBIcon* icon = [self iconForDisplayIdentifier:id])
		if (UIView* v = [icon viewWithTag:90210])
			[v setNeedsDisplay];
}

MSHook(void, _significantTimeChange, SBCalendarController *self, SEL sel) 
{
	__significantTimeChange(self, sel);
	SBIconModel* model = [$SBIconModel sharedInstance];
	if (SBIcon* icon = [model iconForDisplayIdentifier:@"com.apple.mobilecal"])
		if (UIView* v = [icon viewWithTag:90210])
			[v setNeedsDisplay];
}

MSHook(void, setHighlighted, SBIcon *self, SEL sel, BOOL value) 
{
	_setHighlighted(self, sel, value);
	if (UIView* view = [self viewWithTag:90210])
		[view setAlpha:(value ? _opacity * 0.60 : _opacity)];
}

MSHook(void, stopJittering, SBIconList *self, SEL sel)
{
	jittering = false;
	_stopJittering(self, sel);
}

MSHook(void, setIsJittering, SBIcon *self, SEL sel, BOOL value) 
{
	_setIsJittering(self, sel, value);

	jittering = jittering || value;
	if (UIView* view = [self viewWithTag:90210])
		[view setNeedsDisplay];
}

MSHook(void, deactivated, SBApplication *self, SEL sel) 
{
	_deactivated(self, sel);

	if ([self.displayIdentifier isEqualToString:@"com.apple.Preferences"])
	{
		NSFileManager* fm = [NSFileManager defaultManager];
		if (NSDictionary* attrs = [fm fileAttributesAtPath:@"/User/Library/Preferences/com.ashman.ReflectiveDock.plist" traverseLink:true])
		{
			if (NSDate* modDate = [attrs objectForKey:NSFileModificationDate])
			{
				if ([modDate timeIntervalSinceReferenceDate] > _lastSettingsLoad)
				{
					_lastSettingsLoad = 0;
					initSettings();
					for (UIView* view in _reflectionViews)
					{
						SBIcon* icon = [view superview];
						UIImage* fromImage = [icon icon];
						view.frame = CGRectMake(_horizontalShift - ((fromImage.size.width - icon.frame.size.width) / 2), 0, fromImage.size.width, fromImage.size.height * 2);
						view.alpha = _opacity;
						[view setNeedsDisplay];
					}	
				}
			}
		}
	}
}

MSHook(void, setInDock, SBIcon *self, SEL sel, BOOL value) 
{
	_setInDock(self, sel, value);

	initSettings();
	BOOL hidden = !(value ? _reflectDock : _reflectApps);
//	NSLog(@"RD: Setting in dock: %@, %d, %d", self.displayIdentifier, value, hidden);
	UIView* view = [self viewWithTag:90210];
	if (hidden)
	{
		if (view)
		{
			[_reflectionViews removeObject:view];
			[view removeFromSuperview];
		}
	}
	else
	{
		if (view == nil)
			createReflection(self);
	}
}

@implementation RDReflectionView 

@synthesize reflection;

-(void) setNeedsDisplay
{
	self.reflection = nil;
	[super setNeedsDisplay];
}

-(void) drawRect:(CGRect) rect
{
	// move on if we're hidden
	if (jittering || self.hidden)
		return;

	SBIcon* icon = [self superview];
	UIImage* fromImage = [icon icon];

        CGRect reflectRect = CGRectMake((fromImage.size.width - icon.frame.size.width) / 2, 0, fromImage.size.width, fromImage.size.height);
//        NSLog(@"RD: Rect: %f, %f, %f, %f", self.frame.origin.x, self.frame.origin.y, self.frame.size.width, self.frame.size.height);
//        NSLog(@"RD: Rect: %f, %f, %f, %f", reflectRect.origin.x, reflectRect.origin.y, reflectRect.size.width, reflectRect.size.height);

	// the amount we move the reflection up is based on the size of the icon
	int xShift = ((int)(icon.frame.size.width - fromImage.size.width - 1) / 2);
	int yShift = ((int)(icon.frame.size.height - fromImage.size.height - 14) / 2);
	//NSLog(@"RD: %@ Shift: %d, %d", icon.displayIdentifier, xShift, yShift);

	if (_length == 0)
	{
	        reflectRect.size.height = reflectRect.size.height * _lengthFactor;
	}
	else
	        reflectRect.size.height = _length;

	int gapReduction = (int)(fromImage.size.height / 12);
        reflectRect = CGRectOffset(reflectRect, xShift, fromImage.size.height + yShift + _gap - gapReduction);

        //NSLog(@"RD: Rect: %f, %f, %f, %f", reflectRect.origin.x, reflectRect.origin.y, reflectRect.size.width, reflectRect.size.height);

	if (self.reflection)
	{
		[self.reflection drawAtPoint:reflectRect.origin];	
		return;
	}

	NSLog(@"RD: Redrawing reflection for %@.", icon.displayIdentifier);
        // create a bitmap graphics context the size of the image
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();

        // create the bitmap context
        CGContextRef bitmapContext = CGBitmapContextCreate (nil, reflectRect.size.width, reflectRect.size.height, 8,
                                                        0, colorSpace,
                                                        // this will give us an optimal BGRA format for the device:
                                                        (kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst));
        CGColorSpaceRelease(colorSpace);
	UIGraphicsPushContext(bitmapContext);

        CGFloat translateVertical = fromImage.size.height - reflectRect.size.height;
        CGContextTranslateCTM(bitmapContext, 0, -translateVertical);

        // render the layer into the bitmap context
        [fromImage drawAtPoint:CGPointMake(0, 0)];

        CGContextTranslateCTM(bitmapContext, -xShift, -yShift);
	NSArray* views = [icon subviews];
	for (int i = 0; i < views.count; i++)
	{
		UIView* view = [views objectAtIndex:i];
		if ([view class] != $SBIconLabel && 
			[view class] != $SBIconBadge &&
			[view class] != $SBIconImageView &&
			[view class] != [self class])
		{
               	 	[view drawRect:CGRectMake(0, 0, reflectRect.size.width, reflectRect.size.height)];
		}
	}
	UIGraphicsPopContext();
        CGContextTranslateCTM(bitmapContext, xShift, yShift);

        // create CGImageRef of the main view bitmap content, and then release that bitmap context
        CGImageRef mainViewContentBitmapContext = CGBitmapContextCreateImage(bitmapContext);
        CGContextRelease(bitmapContext);

	CGImageRef reflectionImage;

        // gradient
	if (_useGradient)
	{
	        CGColorSpaceRef colorSpace2 = CGColorSpaceCreateDeviceGray();
	      	CGContextRef gradientBitmapContext = CGBitmapContextCreate (NULL, 1, reflectRect.size.height,8, 0, colorSpace2, kCGImageAlphaNone);
	        CGFloat colors[] = {0.0, 1.0, 1.0, 1.0};
	        CGGradientRef grayScaleGradient = CGGradientCreateWithColorComponents(colorSpace2, colors, NULL, 2);
	        CGPoint gradientStartPoint = CGPointZero;
	        CGPoint gradientEndPoint = CGPointMake(0, reflectRect.size.height);

	        CGContextDrawLinearGradient (gradientBitmapContext, grayScaleGradient, gradientStartPoint, gradientEndPoint, kCGGradientDrawsAfterEndLocation);
	        CGGradientRelease(grayScaleGradient);
	        CGImageRef gradientMaskImage = CGBitmapContextCreateImage(gradientBitmapContext);
	        CGContextRelease(gradientBitmapContext); CGColorSpaceRelease(colorSpace2);
	
	        reflectionImage = CGImageCreateWithMask(mainViewContentBitmapContext, gradientMaskImage);
        	CGImageRelease(gradientMaskImage);
	}
	else
	{
		CGRect maskRect = CGRectMake(0, 0, reflectRect.size.width, reflectRect.size.height);
        	reflectionImage = CGImageCreateWithImageInRect(mainViewContentBitmapContext, maskRect);
	}

        CGImageRelease(mainViewContentBitmapContext);
 
        // convert the finished reflection image to a UIImage
        UIImage *theImage = [UIImage imageWithCGImage:reflectionImage];

        // image is retained by the property setting above, so we can release the original
        CGImageRelease(reflectionImage);
       
	self.reflection = theImage; 

	[theImage drawAtPoint:reflectRect.origin];	
}

@end

#define Hook(cls, sel, imp) \
        _ ## imp = MSHookMessage($ ## cls, @selector(sel), &$ ## imp)

extern "C" void ReflectiveDockInitialize() {
	if (objc_getClass("SpringBoard") == nil)
		return;

	// create reflection view
	_reflectionViews = [[NSMutableSet alloc] initWithCapacity:20];

	// Get the SBIcon class
	Class $SBIcon = objc_getClass("SBIcon");
	Class $SBIconModel = objc_getClass("SBIconModel");
	Class $SBIconList = objc_getClass("SBIconList");
	Class $SBApplication = objc_getClass("SBApplication");
	Class $SBApplicationIcon = objc_getClass("SBApplicationIcon");
	Class $SBBookmarkIcon = objc_getClass("SBBookmarkIcon");
	Class $SBCalendarController = objc_getClass("SBCalendarController");
	$SBIconController = objc_getClass("SBIconController");
	
	Hook(SBApplication, deactivated, deactivated);
	Hook(SBIcon, setInDock:, setInDock);
	Hook(SBIcon, setHighlighted:, setHighlighted);
	Hook(SBIcon, setIsJittering:, setIsJittering);
	Hook(SBIconList, stopJittering, stopJittering);
	Hook(SBIconModel, reloadIconImageForDisplayIdentifier:, reloadIconImageForDisplayIdentifier);
	Hook(SBCalendarController, _significantTimeChange, _significantTimeChange);
}
